#include <openssl/core.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/provider.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(_WIN32) || defined(WIN32)
#define LIB_NAME(name) #name
#else
#define LIB_NAME(name) "lib" #name
#endif

static uint8_t key128128[16] = {
    0xdd, 0xc0, 0x2e, 0x33, 0x71, 0xeb, 0xa4, 0x4d,
    0x24, 0x24, 0x7f, 0xba, 0x9b, 0xf6, 0x72, 0x72,
};
static uint8_t pt128128[16] = {
    0x24, 0xa7, 0xa8, 0x07, 0x02, 0xde, 0x70, 0xce,
    0xd4, 0xc9, 0x14, 0x16, 0xb1, 0x9b, 0x1c, 0xbd,
};
static uint8_t ct128128[16] = {
    0xe9, 0xd3, 0x07, 0x2e, 0x61, 0xbf, 0xb7, 0xaa,
    0x3b, 0x14, 0xd9, 0x64, 0xda, 0x6a, 0xa4, 0x24,
};

#define PRINT(fmt, ...) \
    printf("[LOG] " fmt " %s:%d\n", ##__VA_ARGS__, __FILE__, __LINE__)

int main()
{
    static uint8_t  buffer[1024 * 1024];
    int             ret;
    EVP_CIPHER*     cipher = NULL;
    EVP_CIPHER_CTX* cctx   = NULL;
    OSSL_PROVIDER*  prov   = NULL;

    // set search path
    ret = OSSL_PROVIDER_set_default_search_path(NULL, "./");
    if (!ret)
    {
        PRINT("Failed to set default search path");
        PRINT("%s", ERR_error_string(ERR_get_error(), NULL));
        exit(0);
    }
    prov = OSSL_PROVIDER_load(NULL, LIB_NAME(gmlibprov));
    if (prov == NULL)
    {
        PRINT("OSSL_PROVIDER_load failed");
        PRINT("%s", ERR_error_string(ERR_get_error(), NULL));
        exit(0);
    }

    printf("[%d] Provider loaded\n", __LINE__);

    // fetch cipher
    cipher = EVP_CIPHER_fetch(NULL, "Ballet-128-ECB-128", "provider=gmlibprov");
    if (cipher == NULL)
    {
        PRINT("EVP_CIPHER_fetch failed");
        PRINT("%s", ERR_error_string(ERR_get_error(), NULL));
        exit(0);
    }
    cctx = EVP_CIPHER_CTX_new();

    printf("[%d] EVP Cipher Ballet-128-ECB-128 start\n", __LINE__);

    ret = EVP_CipherInit(cctx, cipher, key128128, NULL, 1);
    if (!ret)
    {
        PRINT("EVP_CipherInit failed");
        PRINT("%s", ERR_error_string(ERR_get_error(), NULL));
        exit(0);
    }

    uint8_t* out  = buffer;
    int      outl = 0;

    ret = EVP_CipherUpdate(cctx, out, &outl, pt128128, sizeof(pt128128));
    if (!ret)
    {
        PRINT("EVP_CipherUpdate failed");
        PRINT("%s", ERR_error_string(ERR_get_error(), NULL));
        exit(0);
    }
    out += outl;

    ret = EVP_CipherFinal(cctx, out, &outl);
    if (!ret)
    {
        PRINT("EVP_CipherFinal failed");
        PRINT("%s", ERR_error_string(ERR_get_error(), NULL));
        exit(0);
    }
    out += outl;

    if ((size_t)out - (size_t)buffer != sizeof(ct128128))
    {
        PRINT("EVP_CipherFinal failed");
        exit(0);
    }
    if (memcmp(ct128128, buffer, sizeof(ct128128)) != 0)
    {
        PRINT("EVP_CipherFinal failed");
        exit(0);
    }

    EVP_CIPHER_CTX_free(cctx);
    EVP_CIPHER_free(cipher);
    OSSL_PROVIDER_unload(prov);

    printf("[%d] EVP CIPHER Ballet-128-ECB-128 PASS\n", __LINE__);

    return 0;
}